Skip to content

Restore proactive OAuth token refresh for Desktop mode#402

Merged
saucow merged 1 commit intodocker:release/0.39.1from
saucow:desktop-proactive-token-refresh
Feb 13, 2026
Merged

Restore proactive OAuth token refresh for Desktop mode#402
saucow merged 1 commit intodocker:release/0.39.1from
saucow:desktop-proactive-token-refresh

Conversation

@saucow
Copy link
Copy Markdown
Contributor

@saucow saucow commented Feb 13, 2026

What I did

Restored the proactive OAuth token refresh Provider loop for Desktop mode. This was removed in #247 (Secrets Engine migration) under the assumption that SSE notifications would handle refresh reactively. In practice, tokens sat in HTTP headers for their full lifetime with no proactive refresh — tool calls would fail once the token expired.

Key changes

  • Provider loop runs in both CE and Desktop modes. Previously only CE mode had a polling loop that tracked token expiry and triggered refresh before tokens expired. Desktop mode relied entirely on reactive SSE events.

  • Desktop mode reads token expiry from Secrets Engine metadata. The GetTokenStatus() function now reads ExpiryAt from the Secrets Engine response metadata in Desktop mode, with graceful fallback when metadata is unavailable (older versions).

  • Restored SSE event routing. EventLoginSuccess is routed to the Provider to trigger reload and reset retry counters. EventLogoutSuccess stops the Provider.

  • EventTokenRefresh handled via connection invalidation only. Token refresh SSE events invalidate cached connections directly (InvalidateOAuthClients) instead of being routed to the Provider. This prevents an infinite loop where reloadFn triggers a Secrets Engine query → the Secrets Engine's Filter() calls RefreshToken() → the oauth2 library returns the same token (still valid) → another SSE event is sent → loop repeats. The Provider's timer handles refresh scheduling independently.

  • Retry safeguards. Exponential backoff (30s → 1m → 2m → ... up to 7 attempts) when a refresh doesn't update the token expiry. Zero-expiry guard prevents busy loop when metadata is missing.

Related issue

Addresses the gap where OAuth tokens expired without proactive refresh in Desktop mode, requiring users to re-authenticate manually after token expiry.

Related: #247

    Secrets Engine response metadata (added in Pinata PR #38931)
  - Restore eventChan, SendEvent, and CE/Desktop refresh split in Provider
  - Restore routeEventToProvider for SSE event routing in run.go
  - Start Provider loops in both CE and Desktop modes
  - Handle EventTokenRefresh via InvalidateOAuthClients directly (not
    routed to Provider) to avoid a reloadFn → Filter → SSE → reloadFn
    infinite loop caused by Pinata's Filter triggering RefreshToken within
    the 5-minute window before the oauth2 library's 10-second threshold
  - Add zero-ExpiresAt guard to prevent busy loop when metadata is missing
@saucow saucow marked this pull request as ready for review February 13, 2026 17:49
@saucow saucow requested a review from a team as a code owner February 13, 2026 17:49
@saucow saucow assigned tuna-docker and unassigned tuna-docker Feb 13, 2026
@saucow saucow requested a review from tuna-docker February 13, 2026 17:50
Comment thread pkg/oauth/provider.go
// Desktop mode: Trigger refresh via Desktop API
go func() {
authClient := desktop.NewAuthClient()
app, err := authClient.GetOAuthApp(context.Background(), p.name)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't be cancelled if the provider stops

Comment thread pkg/oauth/provider.go
return &Provider{
name: name,
stopChan: make(chan struct{}),
eventChan: make(chan Event),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trying to think if this chan needs any buffering just in case we get a send before a receive but I don't think that's possible here.

@saucow saucow merged commit 16dd2f2 into docker:release/0.39.1 Feb 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants